package com.baidu.iit.pxp.el.juel;

import com.baidu.iit.pxp.el.*;

import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.PrintWriter;
import java.util.Arrays;

/**
 * User: huangweili
 * Date: 14-4-29
 * Time: 下午2:56
 */
public final class TreeMethodExpression extends MethodExpression {
    private static final long serialVersionUID = 1L;

    private final TreeBuilder builder;
    private final Bindings bindings;
    private final String expr;
    private final Class<?> type;
    private final Class<?>[] types;
    private final boolean deferred;

    private transient ExpressionNode node;

    private String structure;


    public TreeMethodExpression(TreeStore store, FunctionMapper functions, VariableMapper variables, TypeConverter converter, String expr, Class<?> returnType, Class<?>[] paramTypes) {
        super();

        Tree tree = store.get(expr);

        this.builder = store.getBuilder();
        this.bindings = tree.bind(functions, variables, converter);
        this.expr = expr;
        this.type = returnType;
        this.types = paramTypes;
        this.node = tree.getRoot();
        this.deferred = tree.isDeferred();

        if (node.isLiteralText()) {
            if (returnType == void.class) {
                throw new ELException("error.method.literal.void");
            }
        } else if (!node.isMethodInvocation()) {
            if (!node.isLeftValue()) {
                throw new ELException("error.method.invalid");
            }
            if (paramTypes == null) {
                throw new ELException("error.method.notypes");
            }
        }
    }

    private String getStructuralId() {
        if (structure == null) {
            structure = node.getStructuralId(bindings);
        }
        return structure;
    }

    /**
     * Evaluates the expression and answers information about the method
     * @param context used to resolve properties (<code>base.property</code> and <code>base[property]</code>)
     * @return method information or <code>null</code> for literal expressions
     * @throws ELException if evaluation fails (e.g. suitable method not found)
     */
    @Override
    public MethodInfo getMethodInfo(ELContext context) throws ELException {
        return node.getMethodInfo(bindings, context, type, types);
    }

    @Override
    public String getExpressionString() {
        return expr;
    }


    @Override
    public Object invoke(ELContext context, Object[] paramValues) throws ELException {
        return node.invoke(bindings, context, type, types, paramValues);
    }

    /**
     * @return <code>true</code> if this is a literal text expression
     */
    @Override
    public boolean isLiteralText() {
        return node.isLiteralText();
    }


    @Override
    public boolean isParmetersProvided() {
        return node.isMethodInvocation();
    }

    public boolean isDeferred() {
        return deferred;
    }


    @Override
    public boolean equals(Object obj) {
        if (obj != null && obj.getClass() == getClass()) {
            TreeMethodExpression other = (TreeMethodExpression)obj;
            if (!builder.equals(other.builder)) {
                return false;
            }
            if (type != other.type) {
                return false;
            }
            if (!Arrays.equals(types, other.types)) {
                return false;
            }
            return getStructuralId().equals(other.getStructuralId()) && bindings.equals(other.bindings);
        }
        return false;
    }

    @Override
    public int hashCode() {
        return getStructuralId().hashCode();
    }

    @Override
    public String toString() {
        return "TreeMethodExpression(" + expr + ")";
    }


    private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException {
        in.defaultReadObject();
        try {
            node = builder.build(expr).getRoot();
        } catch (ELException e) {
            throw new IOException(e.getMessage());
        }
    }

    public void dump(PrintWriter writer) {
        NodePrinter.dump(writer, node);
    }
}
